/*
 * SPDX-FileCopyrightText: 2024 Espressif Systems (Shanghai) CO LTD
 *
 * SPDX-License-Identifier: Unlicense OR CC0-1.0
 */
/* Includes */
/* STD APIs */
#include <stdbool.h>
#include <stdio.h>
#include "stdint.h"
#include <string.h>

/* ESP APIs */
#include "esp_log.h"

/* FreeRTOS APIs */
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>

#include "DevicesQueue.h"
#include "taskNetwork.h"

/* NimBLE GATT APIs */
#include "services/gatt/ble_svc_gatt.h"

/* NimBLE stack APIs */
#include "host/ble_hs.h"
#include "host/ble_uuid.h"
#include "gatt_svc.h"


static const char *TAG = __FILE_NAME__;

/* Private function declarations */
static int gatt_chr_access(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt *ctxt, void *arg);

/* Private variables */
/* Heart rate service */
static const ble_uuid16_t heart_rate_svc_uuid = BLE_UUID16_INIT(0x181C);

static uint16_t read_chr_val_handle;
static const ble_uuid16_t read_chr_uuid = BLE_UUID16_INIT(0x0001);
static uint16_t read_chr_conn_handle = 0;
static uint8_t read_cur_notify = 0;

static uint16_t write_chr_val_handle;
static const ble_uuid16_t write_chr_uuid = BLE_UUID16_INIT(0x0002);
static uint16_t write_chr_conn_handle = 0;
static uint8_t write_cur_notify = 0;

static uint16_t message_chr_val_handle;
static const ble_uuid16_t message_chr_uuid = BLE_UUID16_INIT(0x0003);
static uint16_t message_chr_conn_handle = 0;
static uint8_t message_cur_notify = 0;

/* GATT services table */
static const struct ble_gatt_svc_def gatt_svr_svcs[] = {
    /* user data service */
    {
        .type = BLE_GATT_SVC_TYPE_PRIMARY,
        .uuid = &heart_rate_svc_uuid.u,
        .characteristics =
            (struct ble_gatt_chr_def[]) {
                /* read characteristic */
                {
                    .uuid = &read_chr_uuid.u,
                    .access_cb = gatt_chr_access,
                    .flags = BLE_GATT_CHR_F_NOTIFY,
                    .val_handle = &read_chr_val_handle
                },
                /* write characteristic */
                {
                    .uuid = &write_chr_uuid.u,
                    .access_cb = gatt_chr_access,
                    .flags = BLE_GATT_CHR_F_WRITE,
                    .val_handle = &write_chr_val_handle
                },
                /* message characteristic */
                {
                    .uuid = &message_chr_uuid.u,
                    .access_cb = gatt_chr_access,
                    .flags = BLE_GATT_CHR_F_WRITE | BLE_GATT_CHR_F_NOTIFY,
                    .val_handle = &message_chr_val_handle
                },
                /* No more characteristics in this service. */
                {
                    0,
                }
        }
    },

    /* No more services. */
    {
        0,
    },
};

static int gatt_chr_access(uint16_t conn_handle, uint16_t attr_handle, struct ble_gatt_access_ctxt *ctxt, void *arg)
{
    /* Local variables */
    int rc;

    /* Handle access events */
    /* Note: LED characteristic is write only */
    switch (ctxt->op)
    {
        /* Read characteristic event */
        case BLE_GATT_ACCESS_OP_READ_CHR:
            /* Verify connection handle */
            if (conn_handle != BLE_HS_CONN_HANDLE_NONE)
            {
                ESP_LOGI(TAG, "characteristic read; conn_handle=%d attr_handle=%d", conn_handle, attr_handle);
            }
            else
            {
                ESP_LOGI(TAG, "characteristic read by nimble stack; attr_handle=%d", attr_handle);
            }

            /* Verify attribute handle */
            if (attr_handle == read_chr_val_handle)
            {
                if((ctxt->om->om_len = iQueueGetLength(&g_TypeQueueBleSend)) > 0)
                {
                    ctxt->om->om_len = (ctxt->om->om_len > sizeof(ctxt->om->om_data)) ? sizeof(ctxt->om->om_data) : ctxt->om->om_len;
                    enumQueuePopDatas(&g_TypeQueueBleSend, ctxt->om->om_data, ctxt->om->om_len);
                }
                /* Update access buffer value */
                rc = os_mbuf_append(ctxt->om, ctxt->om->om_data, ctxt->om->om_len);
                return rc == 0 ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES;
            }
            else if (attr_handle == message_chr_val_handle)
            {
                if((ctxt->om->om_len = iQueueGetLength(&g_TypeQueueBleSend)) > 0)
                {
                    ctxt->om->om_len = (ctxt->om->om_len > sizeof(ctxt->om->om_data)) ? sizeof(ctxt->om->om_data) : ctxt->om->om_len;
                    enumQueuePopDatas(&g_TypeQueueBleSend, ctxt->om->om_data, ctxt->om->om_len);
                }
                /* Update access buffer value */
                rc = os_mbuf_append(ctxt->om, ctxt->om->om_data, ctxt->om->om_len);
                return rc == 0 ? 0 : BLE_ATT_ERR_INSUFFICIENT_RES;
            }
            goto error;

        /* Write characteristic event */
        case BLE_GATT_ACCESS_OP_WRITE_CHR:
            /* Verify connection handle */
            if (conn_handle != BLE_HS_CONN_HANDLE_NONE)
            {
                ESP_LOGI(TAG, "characteristic write; conn_handle=%d attr_handle=%d", conn_handle, attr_handle);
            }
            else
            {
                ESP_LOGI(TAG, "characteristic write by nimble stack; attr_handle=%d", attr_handle);
            }

            /* Verify attribute handle */
            if (attr_handle == write_chr_val_handle)
            {
                if(enumQueuePushDatas(&g_TypeQueueBleRead, ctxt->om->om_data, strnlen((char *)ctxt->om->om_data, ctxt->om->om_len)) != queueNormal)
                    enumQueueClear(&g_TypeQueueBleRead);

                vTaskNotifyGiveFromISR(g_TaskNetworkHand, NULL);

                return 0;
            }
            else if (attr_handle == message_chr_val_handle)
            {
                if(enumQueuePushDatas(&g_TypeQueueBleRead, ctxt->om->om_data, strnlen((char *)ctxt->om->om_data, ctxt->om->om_len)) != queueNormal)
                    enumQueueClear(&g_TypeQueueBleRead);

                vTaskNotifyGiveFromISR(g_TaskNetworkHand, NULL);

                return 0;
            }

        /* Unknown event */
        default: goto error;
    }

error:
    ESP_LOGE(TAG, "unexpected access operation to led characteristic, opcode: %d", ctxt->op);

    return BLE_ATT_ERR_UNLIKELY;
}

/*
 *  Handle GATT attribute register events
 *      - Service register event
 *      - Characteristic register event
 *      - Descriptor register event
 */
void gatt_svr_register_cb(struct ble_gatt_register_ctxt *ctxt, void *arg)
{
    /* Local variables */
    char buf[BLE_UUID_STR_LEN] = {0};

    /* Handle GATT attributes register events */
    switch (ctxt->op)
    {
        /* Service register event */
        case BLE_GATT_REGISTER_OP_SVC:
            ESP_LOGD(TAG, "registered service %s with handle=%d",
                ble_uuid_to_str(ctxt->svc.svc_def->uuid, buf), ctxt->svc.handle);
            break;

        /* Characteristic register event */
        case BLE_GATT_REGISTER_OP_CHR:
            ESP_LOGD(TAG,
                "registering characteristic %s with " "def_handle=%d val_handle=%d",
                ble_uuid_to_str(ctxt->chr.chr_def->uuid, buf), ctxt->chr.def_handle, ctxt->chr.val_handle);
            break;

        /* Descriptor register event */
        case BLE_GATT_REGISTER_OP_DSC:
            ESP_LOGD(TAG, "registering descriptor %s with handle=%d",
                ble_uuid_to_str(ctxt->dsc.dsc_def->uuid, buf), ctxt->dsc.handle);
            break;

        /* Unknown event */
        default: assert(0); break;
    }
}

/*
 *  GATT server subscribe event callback
 *      1. Update heart rate subscription status
 */

void gatt_svr_subscribe_cb(struct ble_gap_event *event)
{
    /* Check connection handle */
    if (event->subscribe.conn_handle != BLE_HS_CONN_HANDLE_NONE)
    {
        ESP_LOGI(TAG, "subscribe event; conn_handle=%d attr_handle=%d", event->subscribe.conn_handle, event->subscribe.attr_handle);
    }
    else
    {
        ESP_LOGI(TAG, "subscribe by nimble stack; attr_handle=%d", event->subscribe.attr_handle);
    }

    /* Check attribute handle */
    if (event->subscribe.attr_handle == read_chr_val_handle)
    {
        /* Update heart rate subscription status */
        read_chr_conn_handle = event->subscribe.conn_handle;
        read_cur_notify = event->subscribe.cur_notify;
    }
    else if (event->subscribe.attr_handle == write_chr_val_handle)
    {
        /* Update heart rate subscription status */
        write_chr_conn_handle = event->subscribe.conn_handle;
        write_cur_notify = event->subscribe.cur_notify;
    }
    else if (event->subscribe.attr_handle == message_chr_val_handle)
    {
        /* Update heart rate subscription status */
        message_chr_conn_handle = event->subscribe.conn_handle;
        message_cur_notify = event->subscribe.cur_notify;
    }
}

/*
 *  GATT server initialization
 *      1. Initialize GATT service
 *      2. Update NimBLE host GATT services counter
 *      3. Add GATT services to server
 */
int gatt_svc_init(void)
{
    /* Local variables */
    int rc;

    /* 1. GATT service initialization */
    ble_svc_gatt_init();

    /* 2. Update GATT services counter */
    if ((rc = ble_gatts_count_cfg(gatt_svr_svcs)) != 0)
    {
        return rc;
    }

    /* 3. Add GATT services */
    if ((rc = ble_gatts_add_svcs(gatt_svr_svcs)) != 0)
    {
        return rc;
    }

    return 0;
}

int8_t ble_datas_send(void *pvDatas, int32_t iLength)
{
    struct os_mbuf *txom;

    if(read_cur_notify != 0)
    {
        txom = ble_hs_mbuf_from_flat(pvDatas, iLength > 512 ? 512 : iLength);

        return ble_gatts_indicate_custom(read_chr_conn_handle, read_chr_val_handle, txom);
    }

    return 1;
}

int8_t ble_message_send(void *pvDatas, int32_t iLength)
{
    struct os_mbuf *txom;

    if(message_cur_notify != 0)
    {
        txom = ble_hs_mbuf_from_flat(pvDatas, iLength > 512 ? 512 : iLength);

        return ble_gatts_indicate_custom(message_chr_conn_handle, message_chr_val_handle, txom);
    }

    return 1;
}
